package model;

import static model.ModelUtils.getAllLeaves;
import static model.ModelUtils.getAllParents;
import static model.ModelUtils.getParents;
import static model.ModelUtils.getSubTreeAsString;
import static model.NodeState.CHECKED_FALSE;
import static model.NodeState.CHECKED_TRUE;
import static model.NodeState.UNCHECKED;

import java.util.List;
import java.util.Stack;

public class TreeNode {

	private NodeState state = UNCHECKED;
	private String type, category;
	private TreeNode parent, nextNode;
	private List<Attribute> attributes;
	private int level;
	private Interrogator interrogator;

	public TreeNode(String category, String type, List<Attribute> attributes,
			Interrogator interrogator) {
		this.category = category;
		this.type = type;
		this.attributes = attributes;
		this.interrogator = interrogator;
	}

	public TreeNode getNextNode() {
		return nextNode;
	}

	public void setNextNode(TreeNode nextNode) {
		this.nextNode = nextNode;
	}

	public TreeNode getParent() {
		return parent;
	}

	public void setParent(TreeNode parent) {
		this.parent = parent;
	}

	public String getType() {
		return type;
	}

	public List<Attribute> getAttributes() {
		return attributes;
	}

	public int getLevel() {
		return level;
	}

	public void setLevel(int level) {
		this.level = level;
	}

	public String toString() {
		return category + ": type=\"" + type + "\" " + attributes;
	}

	public String forRule() {
		return category + "(" + type + ")";
	}

	public boolean check() {
		Stack<TreeNode> allParents = getParents(this);
		TreeNode currentParent;
		while (!allParents.isEmpty()) {
			currentParent = allParents.pop();
			switch (currentParent.state) {
			case CHECKED_FALSE:
				return false;
			case CHECKED_TRUE:
				break;
			case UNCHECKED:
				if (!currentParent.checkAllAttributes()) {
					return false;
				}
				break;
			default:
				break;
			}
		}
		return checkAllAttributes();
	}

	private boolean checkAllAttributes() {
		Answer answer;
		for (Attribute currentAttribute : attributes) {
			answer = interrogator.ask(currentAttribute, this);
			switch (answer) {
			case NO:
				this.state = CHECKED_FALSE;
				return false;
			case WHY:
			case YES:
				break;
			default:
				break;
			}
		}
		this.state = CHECKED_TRUE;
		return true;
	}

	public String why() {
		return "Because I am checking if your element is: " + type + " "
				+ attributes + '\n';
	}

	public String how() {
		return "How:\n" + getSubTreeAsString(this);
	}

	public boolean equals(Object otherNode) {
		if (!(otherNode instanceof TreeNode)) {
			return false;
		}
		TreeNode node = (TreeNode) otherNode;
		if (!node.state.equals(state)) {
			return false;
		}
		if (!node.type.equals(type)) {
			return false;
		}
		if (node.parent != null) {
			if (!node.parent.equals(parent)) {
				return false;
			}
		}
		if (node.nextNode != null) {
			if (!node.nextNode.equals(nextNode)) {
				return false;
			}
		}
		if (node.attributes.size() != attributes.size()) {
			return false;
		}
		for (int index = 0; index < attributes.size(); index++) {
			if (!node.attributes.get(index).equals(attributes.get(index))) {
				return false;
			}
		}
		return true;
	}

	public int hashCode() {
		return state.hashCode() + type.hashCode() + category.hashCode()
				+ parent.hashCode() + attributes.hashCode()
				+ nextNode.hashCode() + level;
	}

	public String getRulesAsString() {
		String rulesAsString = "";
		int ruleNumber = 1;
		int maxLevel = level;
		List<TreeNode> allNodes = getAllParents(this);
		allNodes.addAll(getAllLeaves(this));
		TreeNode parent;
		for (int level = 0; level <= maxLevel; level++) {
			for (TreeNode node : allNodes) {
				if (node.level == level) {
					rulesAsString = rulesAsString + "Rule" + ruleNumber + ": "
							+ node.forRule() + " = ";
					parent = node.parent;
					if (parent == null) {
						rulesAsString = rulesAsString + "top element";
					} else {
						rulesAsString = rulesAsString + node.parent.forRule();
					}
					rulesAsString = rulesAsString + " and " + node.attributes
							+ '\n';
					ruleNumber++;
				}
			}
		}
		return rulesAsString;
	}

	public void resetState() {
		state = UNCHECKED;
	}

}
